/*
 * Copyright (c) 2002-2025 Gargoyle Software Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.htmlunit;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Servlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.htmlunit.html.HtmlPage;
import org.htmlunit.html.XHtmlPage;
import org.htmlunit.xml.XmlPage;
import org.junit.jupiter.api.Test;

/**
 * Tests for {@link DefaultPageCreator}.
 *
 * @author Marc Guillemot
 * @author Ahmed Ashour
 * @author Ronald Brill
 */
public class DefaultPageCreatorTest extends WebServerTestCase {

    /**
     * Verifies page types generated by various combinations of content types, doctypes and namespaces.
     * Uses a real web server so that results can be easily verified against real browsers.
     * @throws Exception if the test fails
     */
    @Test
    public void contentTypes() throws Exception {
        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/x", ContentTypeServlet.class);
        startWebServer("./", null, servlets);

        final WebClient c = getWebClient();
        final String base = URL_FIRST + "x?";

        assertTrue(c.getPage(base + "type=text%2Fhtml") instanceof HtmlPage);
        assertTrue(c.getPage(base + "type=text%2Fhtml&doctype=1") instanceof HtmlPage);
        assertTrue(c.getPage(base + "type=text%2Fhtml&ns=1") instanceof HtmlPage);
        assertTrue(c.getPage(base + "type=text%2Fhtml&doctype=1&ns=1") instanceof HtmlPage);

        assertTrue(c.getPage(base + "type=text%2Fxhtml") instanceof TextPage);
        assertTrue(c.getPage(base + "type=text%2Fxhtml&doctype=1") instanceof TextPage);
        assertTrue(c.getPage(base + "type=text%2Fxhtml&ns=1") instanceof TextPage);
        assertTrue(c.getPage(base + "type=text%2Fxhtml&doctype=1&ns=1") instanceof TextPage);

        assertTrue(c.getPage(base + "type=text%2Fxml") instanceof XmlPage);
        assertTrue(c.getPage(base + "type=text%2Fxml&doctype=1") instanceof XmlPage);
        assertTrue(c.getPage(base + "type=text%2Fxml&ns=1") instanceof XHtmlPage);
        assertTrue(c.getPage(base + "type=text%2Fxml&doctype=1&ns=1") instanceof XHtmlPage);

        assertTrue(c.getPage(base + "type=application%2Fxhtml%2Bxml") instanceof XmlPage);
        assertTrue(c.getPage(base + "type=application%2Fxhtml%2Bxml&doctype=1") instanceof XmlPage);
        assertTrue(c.getPage(base + "type=application%2Fxhtml%2Bxml&ns=1") instanceof XHtmlPage);
        assertTrue(c.getPage(base + "type=application%2Fxhtml%2Bxml&doctype=1&ns=1") instanceof XHtmlPage);
    }

    /**
     * Servlet for {@link #contentTypes()}.
     */
    public static class ContentTypeServlet extends HttpServlet {
        private static final String XHTML_DOCTYPE =
              "<!DOCTYPE html PUBLIC\n"
            + "\"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
            + "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";

        /** {@inheritDoc} */
        @Override
        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
            response.setContentType(request.getParameter("type"));
            try (Writer writer = response.getWriter()) {
                final boolean doctype = request.getParameter("doctype") != null;
                if (doctype) {
                    writer.write(XHTML_DOCTYPE);
                }
                writer.write("<html");
                final boolean ns = request.getParameter("ns") != null;
                if (ns) {
                    writer.write(" xmlns='http://www.w3.org/1999/xhtml'");
                }
                writer.write("><body>foo</body></html>");
            }
        }
    }

    /**
     * @throws Exception if the test fails
     */
    @Test
    public void noContentTypeXhtml() throws Exception {
        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/test", NoContentTypeXhtmlServlet.class);
        startWebServer("./", null, servlets);

        final WebClient client = getWebClient();
        final XHtmlPage page = client.getPage(URL_FIRST + "test");
        assertNotNull(page);
    }

    /**
     * Servlet for {@link #noContentTypeLargeXhtmlHeader()}.
     */
    public static class NoContentTypeXhtmlServlet extends HttpServlet {
        /** {@inheritDoc} */
        @Override
        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
            final Writer writer = response.getWriter();
            writer.write("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n"
                    + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" "
                            + "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\r\n"
                    + "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"lt\" lang=\"lt\">\r\n"
                    + "<body>Hello World</body>\r\n"
                    + "</html>");
        }
    }

    /**
     * @throws Exception if the test fails
     */
    @Test
    public void noContentTypeXhtmlLeadingBlank() throws Exception {
        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/test", NoContentTypeXhtmlLeadingBlankServlet.class);
        startWebServer("./", null, servlets);

        final WebClient client = getWebClient();
        final XHtmlPage page = client.getPage(URL_FIRST + "test");
        assertNotNull(page);
    }

    /**
     * Servlet for {@link #noContentTypeLargeXhtmlHeader()}.
     */
    public static class NoContentTypeXhtmlLeadingBlankServlet extends HttpServlet {
        /** {@inheritDoc} */
        @Override
        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
            final Writer writer = response.getWriter();
            writer.write(" <?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n"
                    + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" "
                            + "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\r\n"
                    + "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"lt\" lang=\"lt\">\r\n"
                    + "<body>Hello World</body>\r\n"
                    + "</html>");
        }
    }

    /**
     * @throws Exception if the test fails
     */
    @Test
    public void noContentTypeXml() throws Exception {
        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/test", NoContentTypeXmlServlet.class);
        startWebServer("./", null, servlets);

        final WebClient client = getWebClient();
        final XmlPage page = client.getPage(URL_FIRST + "test");
        assertNotNull(page);
    }

    /**
     * Servlet for {@link #noContentTypeLargeXmlHeader()}.
     */
    public static class NoContentTypeXmlServlet extends HttpServlet {
        /** {@inheritDoc} */
        @Override
        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
            final Writer writer = response.getWriter();
            writer.write("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n"
                    + "<root>Hello World</root>");
        }
    }

    /**
     * @throws Exception if the test fails
     */
    @Test
    public void noContentTypeXmlLeadingBlank() throws Exception {
        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/test", NoContentTypeXmlLeadingBlankServlet.class);
        startWebServer("./", null, servlets);

        final WebClient client = getWebClient();
        final XmlPage page = client.getPage(URL_FIRST + "test");
        assertNotNull(page);
    }

    /**
     * Servlet for {@link #noContentTypeLargeXmlHeader()}.
     */
    public static class NoContentTypeXmlLeadingBlankServlet extends HttpServlet {
        /** {@inheritDoc} */
        @Override
        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
            final Writer writer = response.getWriter();
            writer.write(" <?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
                    + "<root>Hello World</root>");
        }
    }

    /**
     * @throws Exception if the test fails
     */
    @Test
    public void noContentTypeDoctype() throws Exception {
        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/test", NoContentTypeDoctypeServlet.class);
        startWebServer("./", null, servlets);

        final WebClient client = getWebClient();
        final HtmlPage page = client.getPage(URL_FIRST + "test");
        assertNotNull(page);
    }

    /**
     * Servlet for {@link #noContentTypeDoctype()}.
     */
    public static class NoContentTypeDoctypeServlet extends HttpServlet {
        /** {@inheritDoc} */
        @Override
        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
            final Writer writer = response.getWriter();
            writer.write("<!DOCTYPE HTML><body>Hello World</body>");
        }
    }

    /**
     * @throws Exception if the test fails
     */
    @Test
    public void noContentTypeHtml() throws Exception {
        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/test", NoContentTypeHtmlServlet.class);
        startWebServer("./", null, servlets);

        final WebClient client = getWebClient();
        final HtmlPage page = client.getPage(URL_FIRST + "test");
        assertNotNull(page);
    }

    /**
     * Servlet for {@link #noContentTypeHtml()}.
     */
    public static class NoContentTypeHtmlServlet extends HttpServlet {
        /** {@inheritDoc} */
        @Override
        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
            final Writer writer = response.getWriter();
            writer.write("  <html  ><head><meta http-equiv='Content-Type' content='text/html'></head>\n"
                + "<body>Hello World</body></html>");
        }
    }

    /**
     * @throws Exception if the test fails
     */
    @Test
    public void noContentTypeHtmlStartsNotWith() throws Exception {
        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/test", NoContentTypeHtmlStartsNotWithServlet.class);
        startWebServer("./", null, servlets);

        final WebClient client = getWebClient();
        final TextPage page = client.getPage(URL_FIRST + "test");
        assertNotNull(page);
    }

    /**
     * Servlet for {@link #noContentTypeHtmlStartsNotWith()}.
     */
    public static class NoContentTypeHtmlStartsNotWithServlet extends HttpServlet {
        /** {@inheritDoc} */
        @Override
        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
            final Writer writer = response.getWriter();
            writer.write("  Just to confuse the russians :-) <html><head></head><body>Hello World</body></html>");
        }
    }

    /**
     * @throws Exception if the test fails
     */
    @Test
    public void noContentTypeHead() throws Exception {
        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/test", NoContentTypeHeadServlet.class);
        startWebServer("./", null, servlets);

        final WebClient client = getWebClient();
        final HtmlPage page = client.getPage(URL_FIRST + "test");
        assertNotNull(page);
    }

    /**
     * Servlet for {@link #noContentTypeHead()}.
     */
    public static class NoContentTypeHeadServlet extends HttpServlet {
        /** {@inheritDoc} */
        @Override
        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
            final Writer writer = response.getWriter();
            writer.write("  <head></head><body>Hello World</body>");
        }
    }

    /**
     * @throws Exception if the test fails
     */
    @Test
    public void noContentTypeScript() throws Exception {
        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/test", NoContentTypeScriptServlet.class);
        startWebServer("./", null, servlets);

        final WebClient client = getWebClient();
        final HtmlPage page = client.getPage(URL_FIRST + "test");
        assertNotNull(page);
    }

    /**
     * Servlet for {@link #noContentTypeScript()}.
     */
    public static class NoContentTypeScriptServlet extends HttpServlet {
        /** {@inheritDoc} */
        @Override
        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
            final Writer writer = response.getWriter();
            writer.write("\n<script>");
        }
    }

    /**
     * @throws Exception if the test fails
     */
    @Test
    public void noContentTypeTitle() throws Exception {
        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/test", NoContentTypeTitleServlet.class);
        startWebServer("./", null, servlets);

        final WebClient client = getWebClient();
        final HtmlPage page = client.getPage(URL_FIRST + "test");
        assertNotNull(page);
    }

    /**
     * Servlet for {@link #noContentTypeTitle()}.
     */
    public static class NoContentTypeTitleServlet extends HttpServlet {
        /** {@inheritDoc} */
        @Override
        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
            final Writer writer = response.getWriter();
            writer.write(DOCTYPE_HTML + "<html><head><title>\u00d3</title></head><body></body></html>");
        }
    }

    /**
     * @throws Exception if the test fails
     */
    @Test
    public void noContentTypeBomUtf8() throws Exception {
        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/test", NoContentTypeBomUtf8Servlet.class);
        startWebServer("./", null, servlets);

        final WebClient client = getWebClient();
        final TextPage page = client.getPage(URL_FIRST + "test");
        assertNotNull(page);
    }

    /**
     * Servlet for {@link #noContentTypeBomUtf8()}.
     */
    public static class NoContentTypeBomUtf8Servlet extends HttpServlet {
        /** {@inheritDoc} */
        @Override
        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
            final Writer writer = response.getWriter();
            writer.write("\u00ef\u00bb\u00bf<html><head></head><body></body></html>");
        }
    }

    /**
     * @throws Exception if the test fails
     */
    @Test
    public void noContentTypeBomUtf16() throws Exception {
        final Map<String, Class<? extends Servlet>> servlets = new HashMap<>();
        servlets.put("/test", NoContentTypeBomUtf16Servlet.class);
        startWebServer("./", null, servlets);

        final WebClient client = getWebClient();
        final TextPage page = client.getPage(URL_FIRST + "test");
        assertNotNull(page);
    }

    /**
     * Servlet for {@link #noContentTypeBomUtf16()}.
     */
    public static class NoContentTypeBomUtf16Servlet extends HttpServlet {
        /** {@inheritDoc} */
        @Override
        protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws IOException {
            final OutputStream output = response.getOutputStream();
            output.write('\u00fe');
            output.write('\u00ff');
            output.flush();
            final Writer writer = new OutputStreamWriter(output, "UTF16");
            writer.write(DOCTYPE_HTML + "<html><head></head><body></body></html>");
            writer.flush();
        }
    }
}
